Performance metrics
All three variants of the model:
metrics <- fits[, .(model_name, sens, spec, roc_auc, sens_session, spec_session, roc_auc_session)]
metrics_long <- melt(metrics, id.vars = "model_name", variable.name = "metric", value.name = "value")
# Identify trial-level vs. session-level metrics
metrics_long[, aggregation_level := ifelse(grepl("session", metric), "Session-level", "Trial-level")]
metrics_long[, aggregation_level := factor(aggregation_level, levels = c("Trial-level", "Session-level"))]
metrics_long[, metric := gsub("_session", "", metric)]
# Replace the model names with more descriptive ones
metrics_long[, model_name := factor(model_name, levels = c("only_performance", "bigram_agnostic", "full"),
labels = c("No key- strokes", "Bigram- agnostic", "Full"))]
# Change the order of the factor
metrics_long[, model_name := fct_relevel(model_name, "Full", "Bigram- agnostic", "No key- strokes")]
# Replace the metric names with more descriptive ones
metrics_long[, metric := factor(metric, levels = c("sens", "spec", "roc_auc"),
labels = c("Sensitivity", "Specificity", "ROC AUC"))]
# Change the order of the factor
metrics_long[, metric := fct_relevel(metric, "Sensitivity", "Specificity", "ROC AUC")]
metrics_avg <- metrics_long[, .(value = median(value)), by = .(model_name, metric, aggregation_level)]
ggplot(metrics_long, aes(x = model_name, y = value, group = interaction(model_name, metric, aggregation_level))) +
facet_wrap(~ metric) +
geom_point(aes(colour = aggregation_level), alpha = .5, size = .75, position = position_jitterdodge(jitter.width = .25, dodge.width = .5)) +
geom_boxplot(aes(fill = aggregation_level), width = .5, alpha = .8, outlier.shape = NA) +
geom_point(data = metrics_avg, aes(x = model_name, y = value), size = 3, position = position_dodge(width = .5)) +
geom_label(data = metrics_avg, aes(x = model_name, y = value, label = paste(format(round(value, 3), nsmall = 1))), vjust = -.15, position = position_dodge(width = .5), alpha = .5, label.size = 0) +
scale_colour_brewer(palette = "Dark2") +
scale_fill_brewer(palette = "Dark2") +
scale_x_discrete(labels = function(x) str_wrap(x, width = 10)) +
coord_cartesian(clip = "off") +
labs(x = "Model variant",
y = "Value",
colour = "Classification",
fill = "Classification") +
theme_ml() +
theme(legend.position = "top",
strip.text = element_text(family = "Lato", size = 13))
Loading required package: showtext
Loading required package: sysfonts
Loading required package: showtextdb
Warning: The `size` argument of `element_rect()` is deprecated as of ggplot2 3.4.0.
ℹ Please use the `linewidth` argument instead.
This warning is displayed once every 8 hours.
Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated.

ggsave(here("output", "metrics_all_models.png"), width = 9, height = 6)
Only the full model:
metrics <- fits[model_name == "full", .(roc_auc, sens, spec, roc_auc_session, sens_session, spec_session)]
metrics_long <- melt(metrics, measure.vars = 1:ncol(metrics), variable.name = "metric", value.name = "value")
# Identify trial-level vs. session-level metrics
metrics_long[, aggregation_level := ifelse(grepl("session", metric), "Session-level", "Trial-level")]
metrics_long[, aggregation_level := factor(aggregation_level, levels = c("Trial-level", "Session-level"))]
metrics_long[, metric := gsub("_session", "", metric)]
# Replace the metric names with more descriptive ones
metrics_long[, metric := factor(metric, levels = c("sens", "spec", "roc_auc"),
labels = c("Sensitivity", "Specificity", "ROC AUC"))]
# Change the order of the factor
metrics_long[, metric := fct_relevel(metric, "Sensitivity", "Specificity", "ROC AUC")]
metrics_avg <- metrics_long[, .(value = median(value)), by = .(metric, aggregation_level)]
p_metrics <- ggplot(metrics_long, aes(x = metric, y = value, group = interaction(metric, aggregation_level))) +
geom_point(aes(colour = aggregation_level), alpha = .5, size = .75, position = position_jitterdodge(jitter.width = .25, dodge.width = .5)) +
geom_boxplot(aes(fill = aggregation_level), width = .5, alpha = .8, outlier.shape = NA) +
geom_point(data = metrics_avg, aes(x = metric, y = value), size = 3, position = position_dodge(width = .5)) +
geom_label(data = metrics_avg, aes(x = metric, y = value, label = paste(format(round(value, 3), nsmall = 1))), vjust = -.5, position = position_dodge(width = .5), alpha = .5, label.size = 0) +
scale_colour_brewer(palette = "Dark2") +
scale_fill_brewer(palette = "Dark2") +
coord_cartesian(clip = "off") +
labs(x = "Metric",
y = "Value",
colour = "Classification",
fill = "Classification") +
theme_ml() +
theme(legend.position = "top")
p_metrics

ggsave(here("output", "metrics_boxplot.png"), width = 9, height = 6)
How does performance change with the amount of training data?
metrics_by_user <- fits[model_name == "full", .(user_id, n_sessions_train, n_trials_train, roc_auc, sens, spec, roc_auc_session, sens_session, spec_session)]
metrics_by_user_long <- melt(metrics_by_user, id.vars = c("user_id", "n_sessions_train", "n_trials_train"), measure.vars = 4:ncol(metrics_by_user), variable.name = "metric", value.name = "value")
# Identify trial-level vs. session-level metrics
metrics_by_user_long[, aggregation_level := ifelse(grepl("session", metric), "Session-level", "Trial-level")]
metrics_by_user_long[, aggregation_level := factor(aggregation_level, levels = c("Trial-level", "Session-level"))]
metrics_by_user_long[, metric := gsub("_session", "", metric)]
# Replace the metric names with more descriptive ones
metrics_by_user_long[, metric := factor(metric, levels = c("roc_auc", "sens", "spec"),
labels = c("ROC AUC", "Sensitivity", "Specificity"))]
# Change the order of the factor
metrics_by_user_long[, metric := fct_relevel(metric, "Sensitivity", "Specificity", "ROC AUC")]
p_metrics_by_sessions <- ggplot(metrics_by_user_long, aes(x = n_sessions_train, y = value, group = interaction(metric, aggregation_level))) +
facet_wrap(~metric, scales = "free_y") +
geom_point(aes(colour = aggregation_level), alpha = .1, size = .75) +
geom_smooth(aes(colour = aggregation_level), method = "gam", se = TRUE, alpha = .25) +
scale_colour_brewer(palette = "Dark2") +
labs(x = "Number of training sessions",
y = "Value",
colour = "Classification") +
theme_ml() +
theme(legend.position = "top",
strip.text = element_text(family = "Lato", size = 13))
p_metrics_by_sessions
`geom_smooth()` using formula = 'y ~ s(x, bs = "cs")'

ggsave(here("output", "metrics_by_training_sessions.png"), width = 9, height = 6)
`geom_smooth()` using formula = 'y ~ s(x, bs = "cs")'
Also plot as a function of the number of training trials:
p_metrics_by_trials <- ggplot(metrics_by_user_long, aes(x = n_trials_train, y = value, group = interaction(metric, aggregation_level))) +
facet_wrap(~metric, scales = "free_y") +
geom_point(aes(colour = aggregation_level), alpha = .1, size = .75) +
geom_smooth(aes(colour = aggregation_level), method = "gam", se = TRUE, alpha = .33) +
scale_colour_brewer(palette = "Dark2") +
scale_x_log10(guide = "axis_logticks") +
labs(x = "Number of training trials",
y = "Value",
colour = "Classification") +
theme_ml() +
theme(legend.position = "top",
strip.text = element_text(family = "Lato", size = 13))
p_metrics_by_trials
`geom_smooth()` using formula = 'y ~ s(x, bs = "cs")'

ggsave(here("output", "metrics_by_training_trials.png"), width = 9, height = 6)
`geom_smooth()` using formula = 'y ~ s(x, bs = "cs")'
Combined metrics plot
p_metrics / p_metrics_by_trials + guides(colour = "none", fill = "none") + plot_annotation(tag_levels = "A")
`geom_smooth()` using formula = 'y ~ s(x, bs = "cs")'

ggsave(here("output", "metrics_combined.png"), width = 9, height = 9)
`geom_smooth()` using formula = 'y ~ s(x, bs = "cs")'
Variable importance
For each user-specific model, variable importance was calculated and
scaled so that the most important variable has a value of 100. We can
calculate average variable importance across all users.
# Select all feature columns
vimp_cols <- setdiff(names(fits), c(names(metrics), "model_name", "n_sessions_train", "n_trials_train", "bal_accuracy", "bal_accuracy_session"))
vimp <- fits[model_name == "full", ..vimp_cols]
vimp_long <- melt(vimp, id.vars = "user_id", variable.name = "variable", value.name = "importance")
# Add a column for the availability (%) of this feature in df
feature_availability <- df[, .(variable = vimp_cols, feature_availability = scales::label_percent(accuracy = .1)(colMeans(!is.na(.SD)))), .SDcols = vimp_cols]
vimp_long <- merge(vimp_long, feature_availability, by = "variable")
# Categorise variables into feature types:
# - Learning performance features: correct, reaction_time, alpha
# - Keystroke features: duration_med, rp_med, rr_med, pr_med, pp_med, pp_perc95, pp_mad, backspace_rate
# - Bigram-specific features: bigram_*
vimp_long[, feature_type := case_when(
grepl("correct|reaction_time|alpha", variable) ~ "Learning performance",
grepl("duration_med|rp_med|rr_med|pr_med|pp_med|pp_perc95|pp_mad|backspace_rate", variable) ~ "Keystroke",
grepl("bigram_", variable) ~ "Bigram-specific",
TRUE ~ "Other"
)]
# Replace learning performance features by more descriptive names
vimp_long[feature_type == "Learning performance", variable := case_when(
variable == "correct" ~ "Correct",
variable == "reaction_time" ~ "Reaction time",
variable == "alpha" ~ "Speed of Forgetting"
)]
# Replace keystroke features by more descriptive names
vimp_long[feature_type == "Keystroke", variable := case_when(
variable == "duration_med" ~ "Duration (hold time)",
variable == "rp_med" ~ "Release-Press (flight time)",
variable == "rr_med" ~ "Release-Release",
variable == "pr_med" ~ "Press-Release",
variable == "pp_med" ~ "Press-Press (IKD)",
variable == "pp_perc95" ~ "Press-Press (IKD) 95th percentile",
variable == "pp_mad" ~ "Press-press MAD",
variable == "backspace_rate" ~ "Backspace rate"
)]
# For bigram-specific features, we can replace the numeric Unicode values by the actual bigram: bigram_69_82 -> "E R"
print_bigram <- function (x) {
# Non-printable characters are replaced by their title
print_char <- function (char) {
char <- case_when(
char == 8 ~ "Backspace",
char == 13 ~ "Enter",
char == 16 ~ "Shift",
char == 17 ~ "Ctrl",
char == 20 ~ "CapsLock",
char == 32 ~ "Space",
TRUE ~ intToUtf8(char)
)
}
char1 <- str_extract(x, "\\d+") |>
as.numeric() |>
print_char()
char2 <- str_extract(x, "\\d+$") |>
as.numeric() |>
print_char()
paste0(char1, " ", char2)
}
vimp_long[feature_type == "Bigram-specific", variable := map_chr(variable, print_bigram)]
# Reorder the feature type levels
vimp_long[, feature_type := factor(feature_type, levels = c("Learning performance", "Keystroke", "Bigram-specific"))]
# How often is each feature available in the data?
vimp_feature_availability <- vimp_long[, .(feature_availability = feature_availability[1]), by = .(variable, feature_type)]
ggplot(vimp_long[!is.na(importance)], aes(x = reorder(variable, (importance)), y = importance)) +
geom_rect(data = NULL, aes(xmin = -Inf, xmax = Inf, ymin = 100, ymax = Inf), fill = "white", colour = "white") +
geom_jitter(aes(colour = feature_type), alpha = .1, size = .75) +
geom_boxplot(aes(fill = feature_type), width = .5, alpha = .8, outlier.shape = NA) +
annotate("text", x = Inf, y = 110, label = "Feature\navailability:", hjust = 1, vjust = -.25, size = rel(3), colour = "grey40") +
geom_text(data = vimp_feature_availability, aes(y = 110, x = variable, label = feature_availability), hjust = 1, vjust = .5, size = rel(3), colour = "grey40") +
labs(x = "Feature",
y = "Variable importance (scaled)",
colour = "Feature type",
fill = "Feature type") +
scale_y_continuous(limits = c(0, 110), breaks = seq(0, 100, 25)) +
scale_colour_brewer(palette = "Dark2") +
scale_fill_brewer(palette = "Dark2") +
coord_flip(clip = "off") +
theme_ml() +
theme(axis.text.y = element_text(size = rel(.75)),
legend.position = "top")

ggsave(here("output", "variable_importance.png"), width = 9, height = 9)
LS0tCnRpdGxlOiAiRXZhbHVhdGUgWEdCb29zdCBtb2RlbCBmaXRzIgpkYXRlOiAnTGFzdCB1cGRhdGVkOiBgciBTeXMuRGF0ZSgpYCcKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogIGdpdGh1Yl9kb2N1bWVudDoKICAgIHRvYzogeWVzCi0tLQoKYGBge3IgbG9hZC1wYWNrYWdlc30KbGlicmFyeShoZXJlKQpsaWJyYXJ5KGZzdCkKbGlicmFyeShkYXRhLnRhYmxlKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHB1cnJyKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoc3RyaW5ncikKbGlicmFyeShmb3JjYXRzKQpsaWJyYXJ5KHBhdGNod29yaykKbGlicmFyeShnZ2NvcnJwbG90KQoKc291cmNlKGhlcmUoInNjcmlwdHMiLCAiMDBfcGxvdHRpbmdfZnVuY3Rpb25zLlIiKSkKYGBgCgoKCiMjIExvYWQgZml0IG1ldHJpY3MKCmBgYHtyIGxvYWQtZml0c30KZml0cyA8LSBmcmVhZChoZXJlKCJkYXRhIiwgIm1vZGVsX2ZpdHMuY3N2IikpCmBgYAoKCkFsc28gbG9hZCBmZWF0dXJlIGRhdGE6CmBgYHtyIGxvYWQtZmVhdHVyZXN9CmRmIDwtIHJlYWRfZnN0KGhlcmUoImRhdGEiLCAicmVzcG9uc2VfZmVhdHVyZXMuZnN0IikpCnNldERUKGRmKQpgYGAKCgojIyBQZXJmb3JtYW5jZSBtZXRyaWNzCgpBbGwgdGhyZWUgdmFyaWFudHMgb2YgdGhlIG1vZGVsOgpgYGB7ciBwbG90LW1ldHJpY3MtbW9kZWwtY29tcGFyaXNvbn0KbWV0cmljcyA8LSBmaXRzWywgLihtb2RlbF9uYW1lLCBzZW5zLCBzcGVjLCByb2NfYXVjLCBzZW5zX3Nlc3Npb24sIHNwZWNfc2Vzc2lvbiwgcm9jX2F1Y19zZXNzaW9uKV0KbWV0cmljc19sb25nIDwtIG1lbHQobWV0cmljcywgaWQudmFycyA9ICJtb2RlbF9uYW1lIiwgdmFyaWFibGUubmFtZSA9ICJtZXRyaWMiLCB2YWx1ZS5uYW1lID0gInZhbHVlIikKCiMgSWRlbnRpZnkgdHJpYWwtbGV2ZWwgdnMuIHNlc3Npb24tbGV2ZWwgbWV0cmljcwptZXRyaWNzX2xvbmdbLCBhZ2dyZWdhdGlvbl9sZXZlbCA6PSBpZmVsc2UoZ3JlcGwoInNlc3Npb24iLCBtZXRyaWMpLCAiU2Vzc2lvbi1sZXZlbCIsICJUcmlhbC1sZXZlbCIpXQptZXRyaWNzX2xvbmdbLCBhZ2dyZWdhdGlvbl9sZXZlbCA6PSBmYWN0b3IoYWdncmVnYXRpb25fbGV2ZWwsIGxldmVscyA9IGMoIlRyaWFsLWxldmVsIiwgIlNlc3Npb24tbGV2ZWwiKSldCm1ldHJpY3NfbG9uZ1ssIG1ldHJpYyA6PSBnc3ViKCJfc2Vzc2lvbiIsICIiLCBtZXRyaWMpXQoKIyBSZXBsYWNlIHRoZSBtb2RlbCBuYW1lcyB3aXRoIG1vcmUgZGVzY3JpcHRpdmUgb25lcwptZXRyaWNzX2xvbmdbLCBtb2RlbF9uYW1lIDo9IGZhY3Rvcihtb2RlbF9uYW1lLCBsZXZlbHMgPSBjKCJvbmx5X3BlcmZvcm1hbmNlIiwgImJpZ3JhbV9hZ25vc3RpYyIsICJmdWxsIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIk5vIGtleS0gc3Ryb2tlcyIsICJCaWdyYW0tIGFnbm9zdGljIiwgIkZ1bGwiKSldCgojIENoYW5nZSB0aGUgb3JkZXIgb2YgdGhlIGZhY3RvcgptZXRyaWNzX2xvbmdbLCBtb2RlbF9uYW1lIDo9IGZjdF9yZWxldmVsKG1vZGVsX25hbWUsICJGdWxsIiwgIkJpZ3JhbS0gYWdub3N0aWMiLCAiTm8ga2V5LSBzdHJva2VzIildCgojIFJlcGxhY2UgdGhlIG1ldHJpYyBuYW1lcyB3aXRoIG1vcmUgZGVzY3JpcHRpdmUgb25lcwptZXRyaWNzX2xvbmdbLCBtZXRyaWMgOj0gZmFjdG9yKG1ldHJpYywgbGV2ZWxzID0gYygic2VucyIsICJzcGVjIiwgInJvY19hdWMiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCJTZW5zaXRpdml0eSIsICJTcGVjaWZpY2l0eSIsICJST0MgQVVDIikpXQoKIyBDaGFuZ2UgdGhlIG9yZGVyIG9mIHRoZSBmYWN0b3IKbWV0cmljc19sb25nWywgbWV0cmljIDo9IGZjdF9yZWxldmVsKG1ldHJpYywgIlNlbnNpdGl2aXR5IiwgIlNwZWNpZmljaXR5IiwgIlJPQyBBVUMiKV0KCm1ldHJpY3NfYXZnIDwtIG1ldHJpY3NfbG9uZ1ssIC4odmFsdWUgPSBtZWRpYW4odmFsdWUpKSwgYnkgPSAuKG1vZGVsX25hbWUsIG1ldHJpYywgYWdncmVnYXRpb25fbGV2ZWwpXQoKZ2dwbG90KG1ldHJpY3NfbG9uZywgYWVzKHggPSBtb2RlbF9uYW1lLCB5ID0gdmFsdWUsIGdyb3VwID0gaW50ZXJhY3Rpb24obW9kZWxfbmFtZSwgbWV0cmljLCBhZ2dyZWdhdGlvbl9sZXZlbCkpKSArCiAgZmFjZXRfd3JhcCh+IG1ldHJpYykgKwogICAgZ2VvbV9wb2ludChhZXMoY29sb3VyID0gYWdncmVnYXRpb25fbGV2ZWwpLCBhbHBoYSA9IC41LCBzaXplID0gLjc1LCBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcmRvZGdlKGppdHRlci53aWR0aCA9IC4yNSwgZG9kZ2Uud2lkdGggPSAuNSkpICsKICAgIGdlb21fYm94cGxvdChhZXMoZmlsbCA9IGFnZ3JlZ2F0aW9uX2xldmVsKSwgd2lkdGggPSAuNSwgYWxwaGEgPSAuOCwgb3V0bGllci5zaGFwZSA9IE5BKSArCiAgZ2VvbV9wb2ludChkYXRhID0gbWV0cmljc19hdmcsIGFlcyh4ID0gbW9kZWxfbmFtZSwgeSA9IHZhbHVlKSwgc2l6ZSA9IDMsIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2Uod2lkdGggPSAuNSkpICsKICBnZW9tX2xhYmVsKGRhdGEgPSBtZXRyaWNzX2F2ZywgYWVzKHggPSBtb2RlbF9uYW1lLCB5ID0gdmFsdWUsIGxhYmVsID0gcGFzdGUoZm9ybWF0KHJvdW5kKHZhbHVlLCAzKSwgbnNtYWxsID0gMSkpKSwgdmp1c3QgPSAtLjE1LCBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKHdpZHRoID0gLjUpLCBhbHBoYSA9IC41LCBsYWJlbC5zaXplID0gMCkgKwogIHNjYWxlX2NvbG91cl9icmV3ZXIocGFsZXR0ZSA9ICJEYXJrMiIpICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIkRhcmsyIikgKwogIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzID0gZnVuY3Rpb24oeCkgc3RyX3dyYXAoeCwgd2lkdGggPSAxMCkpICsKICBjb29yZF9jYXJ0ZXNpYW4oY2xpcCA9ICJvZmYiKSArCiAgbGFicyh4ID0gIk1vZGVsIHZhcmlhbnQiLAogICAgICAgeSA9ICJWYWx1ZSIsCiAgICAgICBjb2xvdXIgPSAiQ2xhc3NpZmljYXRpb24iLAogICAgICAgZmlsbCA9ICJDbGFzc2lmaWNhdGlvbiIpICsKICB0aGVtZV9tbCgpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIiwKICAgICAgICBzdHJpcC50ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJMYXRvIiwgc2l6ZSA9IDEzKSkKCmdnc2F2ZShoZXJlKCJvdXRwdXQiLCAibWV0cmljc19hbGxfbW9kZWxzLnBuZyIpLCB3aWR0aCA9IDksIGhlaWdodCA9IDYpCmBgYAoKT25seSB0aGUgZnVsbCBtb2RlbDoKYGBge3IgcGxvdC1tZXRyaWNzLWJveHBsb3R9Cm1ldHJpY3MgPC0gZml0c1ttb2RlbF9uYW1lID09ICJmdWxsIiwgLihyb2NfYXVjLCBzZW5zLCBzcGVjLCByb2NfYXVjX3Nlc3Npb24sIHNlbnNfc2Vzc2lvbiwgc3BlY19zZXNzaW9uKV0KbWV0cmljc19sb25nIDwtIG1lbHQobWV0cmljcywgbWVhc3VyZS52YXJzID0gMTpuY29sKG1ldHJpY3MpLCB2YXJpYWJsZS5uYW1lID0gIm1ldHJpYyIsIHZhbHVlLm5hbWUgPSAidmFsdWUiKQoKIyBJZGVudGlmeSB0cmlhbC1sZXZlbCB2cy4gc2Vzc2lvbi1sZXZlbCBtZXRyaWNzCm1ldHJpY3NfbG9uZ1ssIGFnZ3JlZ2F0aW9uX2xldmVsIDo9IGlmZWxzZShncmVwbCgic2Vzc2lvbiIsIG1ldHJpYyksICJTZXNzaW9uLWxldmVsIiwgIlRyaWFsLWxldmVsIildCm1ldHJpY3NfbG9uZ1ssIGFnZ3JlZ2F0aW9uX2xldmVsIDo9IGZhY3RvcihhZ2dyZWdhdGlvbl9sZXZlbCwgbGV2ZWxzID0gYygiVHJpYWwtbGV2ZWwiLCAiU2Vzc2lvbi1sZXZlbCIpKV0KbWV0cmljc19sb25nWywgbWV0cmljIDo9IGdzdWIoIl9zZXNzaW9uIiwgIiIsIG1ldHJpYyldCgojIFJlcGxhY2UgdGhlIG1ldHJpYyBuYW1lcyB3aXRoIG1vcmUgZGVzY3JpcHRpdmUgb25lcwptZXRyaWNzX2xvbmdbLCBtZXRyaWMgOj0gZmFjdG9yKG1ldHJpYywgbGV2ZWxzID0gYygic2VucyIsICJzcGVjIiwgInJvY19hdWMiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCJTZW5zaXRpdml0eSIsICJTcGVjaWZpY2l0eSIsICJST0MgQVVDIikpXQoKIyBDaGFuZ2UgdGhlIG9yZGVyIG9mIHRoZSBmYWN0b3IKbWV0cmljc19sb25nWywgbWV0cmljIDo9IGZjdF9yZWxldmVsKG1ldHJpYywgIlNlbnNpdGl2aXR5IiwgIlNwZWNpZmljaXR5IiwgIlJPQyBBVUMiKV0KCm1ldHJpY3NfYXZnIDwtIG1ldHJpY3NfbG9uZ1ssIC4odmFsdWUgPSBtZWRpYW4odmFsdWUpKSwgYnkgPSAuKG1ldHJpYywgYWdncmVnYXRpb25fbGV2ZWwpXQoKCnBfbWV0cmljcyA8LSBnZ3Bsb3QobWV0cmljc19sb25nLCBhZXMoeCA9IG1ldHJpYywgeSA9IHZhbHVlLCBncm91cCA9IGludGVyYWN0aW9uKG1ldHJpYywgYWdncmVnYXRpb25fbGV2ZWwpKSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG91ciA9IGFnZ3JlZ2F0aW9uX2xldmVsKSwgYWxwaGEgPSAuNSwgc2l6ZSA9IC43NSwgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXJkb2RnZShqaXR0ZXIud2lkdGggPSAuMjUsIGRvZGdlLndpZHRoID0gLjUpKSArCiAgZ2VvbV9ib3hwbG90KGFlcyhmaWxsID0gYWdncmVnYXRpb25fbGV2ZWwpLCB3aWR0aCA9IC41LCBhbHBoYSA9IC44LCBvdXRsaWVyLnNoYXBlID0gTkEpICsKICBnZW9tX3BvaW50KGRhdGEgPSBtZXRyaWNzX2F2ZywgYWVzKHggPSBtZXRyaWMsIHkgPSB2YWx1ZSksIHNpemUgPSAzLCBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKHdpZHRoID0gLjUpKSArCiAgZ2VvbV9sYWJlbChkYXRhID0gbWV0cmljc19hdmcsIGFlcyh4ID0gbWV0cmljLCB5ID0gdmFsdWUsIGxhYmVsID0gcGFzdGUoZm9ybWF0KHJvdW5kKHZhbHVlLCAzKSwgbnNtYWxsID0gMSkpKSwgdmp1c3QgPSAtLjUsIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2Uod2lkdGggPSAuNSksIGFscGhhID0gLjUsIGxhYmVsLnNpemUgPSAwKSArCiAgc2NhbGVfY29sb3VyX2JyZXdlcihwYWxldHRlID0gIkRhcmsyIikgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiRGFyazIiKSArCiAgY29vcmRfY2FydGVzaWFuKGNsaXAgPSAib2ZmIikgKwogIGxhYnMoeCA9ICJNZXRyaWMiLAogICAgICAgeSA9ICJWYWx1ZSIsCiAgICAgICBjb2xvdXIgPSAiQ2xhc3NpZmljYXRpb24iLAogICAgICAgZmlsbCA9ICJDbGFzc2lmaWNhdGlvbiIpICsKICB0aGVtZV9tbCgpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIikKCnBfbWV0cmljcwoKZ2dzYXZlKGhlcmUoIm91dHB1dCIsICJtZXRyaWNzX2JveHBsb3QucG5nIiksIHdpZHRoID0gOSwgaGVpZ2h0ID0gNikKYGBgCgoKIyMjIEhvdyBkb2VzIHBlcmZvcm1hbmNlIGNoYW5nZSB3aXRoIHRoZSBhbW91bnQgb2YgdHJhaW5pbmcgZGF0YT8KCmBgYHtyIHBsb3QtbWV0cmljcy1ieS10cmFpbmluZy1zZXNzaW9uc30KbWV0cmljc19ieV91c2VyIDwtIGZpdHNbbW9kZWxfbmFtZSA9PSAiZnVsbCIsIC4odXNlcl9pZCwgbl9zZXNzaW9uc190cmFpbiwgbl90cmlhbHNfdHJhaW4sIHJvY19hdWMsIHNlbnMsIHNwZWMsIHJvY19hdWNfc2Vzc2lvbiwgc2Vuc19zZXNzaW9uLCBzcGVjX3Nlc3Npb24pXQoKbWV0cmljc19ieV91c2VyX2xvbmcgPC0gbWVsdChtZXRyaWNzX2J5X3VzZXIsIGlkLnZhcnMgPSBjKCJ1c2VyX2lkIiwgIm5fc2Vzc2lvbnNfdHJhaW4iLCAibl90cmlhbHNfdHJhaW4iKSwgbWVhc3VyZS52YXJzID0gNDpuY29sKG1ldHJpY3NfYnlfdXNlciksIHZhcmlhYmxlLm5hbWUgPSAibWV0cmljIiwgdmFsdWUubmFtZSA9ICJ2YWx1ZSIpCgojIElkZW50aWZ5IHRyaWFsLWxldmVsIHZzLiBzZXNzaW9uLWxldmVsIG1ldHJpY3MKbWV0cmljc19ieV91c2VyX2xvbmdbLCBhZ2dyZWdhdGlvbl9sZXZlbCA6PSBpZmVsc2UoZ3JlcGwoInNlc3Npb24iLCBtZXRyaWMpLCAiU2Vzc2lvbi1sZXZlbCIsICJUcmlhbC1sZXZlbCIpXQptZXRyaWNzX2J5X3VzZXJfbG9uZ1ssIGFnZ3JlZ2F0aW9uX2xldmVsIDo9IGZhY3RvcihhZ2dyZWdhdGlvbl9sZXZlbCwgbGV2ZWxzID0gYygiVHJpYWwtbGV2ZWwiLCAiU2Vzc2lvbi1sZXZlbCIpKV0KbWV0cmljc19ieV91c2VyX2xvbmdbLCBtZXRyaWMgOj0gZ3N1YigiX3Nlc3Npb24iLCAiIiwgbWV0cmljKV0KCiMgUmVwbGFjZSB0aGUgbWV0cmljIG5hbWVzIHdpdGggbW9yZSBkZXNjcmlwdGl2ZSBvbmVzCm1ldHJpY3NfYnlfdXNlcl9sb25nWywgbWV0cmljIDo9IGZhY3RvcihtZXRyaWMsIGxldmVscyA9IGMoInJvY19hdWMiLCAic2VucyIsICJzcGVjIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiUk9DIEFVQyIsICJTZW5zaXRpdml0eSIsICJTcGVjaWZpY2l0eSIpKV0KCiMgQ2hhbmdlIHRoZSBvcmRlciBvZiB0aGUgZmFjdG9yCm1ldHJpY3NfYnlfdXNlcl9sb25nWywgbWV0cmljIDo9IGZjdF9yZWxldmVsKG1ldHJpYywgIlNlbnNpdGl2aXR5IiwgIlNwZWNpZmljaXR5IiwgIlJPQyBBVUMiKV0KCnBfbWV0cmljc19ieV9zZXNzaW9ucyA8LSBnZ3Bsb3QobWV0cmljc19ieV91c2VyX2xvbmcsIGFlcyh4ID0gbl9zZXNzaW9uc190cmFpbiwgeSA9IHZhbHVlLCBncm91cCA9IGludGVyYWN0aW9uKG1ldHJpYywgYWdncmVnYXRpb25fbGV2ZWwpKSkgKwogIGZhY2V0X3dyYXAofm1ldHJpYywgc2NhbGVzID0gImZyZWVfeSIpICsKICBnZW9tX3BvaW50KGFlcyhjb2xvdXIgPSBhZ2dyZWdhdGlvbl9sZXZlbCksIGFscGhhID0gLjEsIHNpemUgPSAuNzUpICsKICBnZW9tX3Ntb290aChhZXMoY29sb3VyID0gYWdncmVnYXRpb25fbGV2ZWwpLCBtZXRob2QgPSAiZ2FtIiwgc2UgPSBUUlVFLCBhbHBoYSA9IC4yNSkgKwogIHNjYWxlX2NvbG91cl9icmV3ZXIocGFsZXR0ZSA9ICJEYXJrMiIpICsKICBsYWJzKHggPSAiTnVtYmVyIG9mIHRyYWluaW5nIHNlc3Npb25zIiwKICAgICAgIHkgPSAiVmFsdWUiLAogICAgICAgY29sb3VyID0gIkNsYXNzaWZpY2F0aW9uIikgKwogIHRoZW1lX21sKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiLAogICAgICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoZmFtaWx5ID0gIkxhdG8iLCBzaXplID0gMTMpKQoKcF9tZXRyaWNzX2J5X3Nlc3Npb25zCgpnZ3NhdmUoaGVyZSgib3V0cHV0IiwgIm1ldHJpY3NfYnlfdHJhaW5pbmdfc2Vzc2lvbnMucG5nIiksIHdpZHRoID0gOSwgaGVpZ2h0ID0gNikKYGBgCgpBbHNvIHBsb3QgYXMgYSBmdW5jdGlvbiBvZiB0aGUgbnVtYmVyIG9mIHRyYWluaW5nIHRyaWFsczoKYGBge3IgcGxvdC1tZXRyaWNzLWJ5LXRyYWluaW5nLXRyaWFsc30KcF9tZXRyaWNzX2J5X3RyaWFscyA8LSBnZ3Bsb3QobWV0cmljc19ieV91c2VyX2xvbmcsIGFlcyh4ID0gbl90cmlhbHNfdHJhaW4sIHkgPSB2YWx1ZSwgZ3JvdXAgPSBpbnRlcmFjdGlvbihtZXRyaWMsIGFnZ3JlZ2F0aW9uX2xldmVsKSkpICsKICBmYWNldF93cmFwKH5tZXRyaWMsIHNjYWxlcyA9ICJmcmVlX3kiKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3VyID0gYWdncmVnYXRpb25fbGV2ZWwpLCBhbHBoYSA9IC4xLCBzaXplID0gLjc1KSArCiAgZ2VvbV9zbW9vdGgoYWVzKGNvbG91ciA9IGFnZ3JlZ2F0aW9uX2xldmVsKSwgbWV0aG9kID0gImdhbSIsIHNlID0gVFJVRSwgYWxwaGEgPSAuMzMpICsKICBzY2FsZV9jb2xvdXJfYnJld2VyKHBhbGV0dGUgPSAiRGFyazIiKSArCiAgc2NhbGVfeF9sb2cxMChndWlkZSA9ICJheGlzX2xvZ3RpY2tzIikgKwogIGxhYnMoeCA9ICJOdW1iZXIgb2YgdHJhaW5pbmcgdHJpYWxzIiwKICAgICAgIHkgPSAiVmFsdWUiLAogICAgICAgY29sb3VyID0gIkNsYXNzaWZpY2F0aW9uIikgKwogIHRoZW1lX21sKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiLAogICAgICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoZmFtaWx5ID0gIkxhdG8iLCBzaXplID0gMTMpKQoKcF9tZXRyaWNzX2J5X3RyaWFscwoKZ2dzYXZlKGhlcmUoIm91dHB1dCIsICJtZXRyaWNzX2J5X3RyYWluaW5nX3RyaWFscy5wbmciKSwgd2lkdGggPSA5LCBoZWlnaHQgPSA2KQpgYGAKCiMjIyBDb21iaW5lZCBtZXRyaWNzIHBsb3QKYGBge3IgcGxvdC1tZXRyaWNzLWNvbWJpbmVkfQpwX21ldHJpY3MgLyBwX21ldHJpY3NfYnlfdHJpYWxzICsgZ3VpZGVzKGNvbG91ciA9ICJub25lIiwgZmlsbCA9ICJub25lIikgKyBwbG90X2Fubm90YXRpb24odGFnX2xldmVscyA9ICJBIikKCmdnc2F2ZShoZXJlKCJvdXRwdXQiLCAibWV0cmljc19jb21iaW5lZC5wbmciKSwgd2lkdGggPSA5LCBoZWlnaHQgPSA5KQpgYGAKCgojIyBWYXJpYWJsZSBpbXBvcnRhbmNlCgpGb3IgZWFjaCB1c2VyLXNwZWNpZmljIG1vZGVsLCB2YXJpYWJsZSBpbXBvcnRhbmNlIHdhcyBjYWxjdWxhdGVkIGFuZCBzY2FsZWQgc28gdGhhdCB0aGUgbW9zdCBpbXBvcnRhbnQgdmFyaWFibGUgaGFzIGEgdmFsdWUgb2YgMTAwLgpXZSBjYW4gY2FsY3VsYXRlIGF2ZXJhZ2UgdmFyaWFibGUgaW1wb3J0YW5jZSBhY3Jvc3MgYWxsIHVzZXJzLgpgYGB7ciB2YXJpYWJsZS1pbXBvcnRhbmNlfQojIFNlbGVjdCBhbGwgZmVhdHVyZSBjb2x1bW5zCnZpbXBfY29scyA8LSBzZXRkaWZmKG5hbWVzKGZpdHMpLCBjKG5hbWVzKG1ldHJpY3MpLCAibW9kZWxfbmFtZSIsICJuX3Nlc3Npb25zX3RyYWluIiwgIm5fdHJpYWxzX3RyYWluIiwgImJhbF9hY2N1cmFjeSIsICJiYWxfYWNjdXJhY3lfc2Vzc2lvbiIpKQoKdmltcCA8LSBmaXRzW21vZGVsX25hbWUgPT0gImZ1bGwiLCAuLnZpbXBfY29sc10KdmltcF9sb25nIDwtIG1lbHQodmltcCwgaWQudmFycyA9ICJ1c2VyX2lkIiwgdmFyaWFibGUubmFtZSA9ICJ2YXJpYWJsZSIsIHZhbHVlLm5hbWUgPSAiaW1wb3J0YW5jZSIpCgojIEFkZCBhIGNvbHVtbiBmb3IgdGhlIGF2YWlsYWJpbGl0eSAoJSkgb2YgdGhpcyBmZWF0dXJlIGluIGRmCmZlYXR1cmVfYXZhaWxhYmlsaXR5IDwtIGRmWywgLih2YXJpYWJsZSA9IHZpbXBfY29scywgZmVhdHVyZV9hdmFpbGFiaWxpdHkgPSBzY2FsZXM6OmxhYmVsX3BlcmNlbnQoYWNjdXJhY3kgPSAuMSkoY29sTWVhbnMoIWlzLm5hKC5TRCkpKSksIC5TRGNvbHMgPSB2aW1wX2NvbHNdCnZpbXBfbG9uZyA8LSBtZXJnZSh2aW1wX2xvbmcsIGZlYXR1cmVfYXZhaWxhYmlsaXR5LCBieSA9ICJ2YXJpYWJsZSIpCgojIENhdGVnb3Jpc2UgdmFyaWFibGVzIGludG8gZmVhdHVyZSB0eXBlczoKIyAtIExlYXJuaW5nIHBlcmZvcm1hbmNlIGZlYXR1cmVzOiBjb3JyZWN0LCByZWFjdGlvbl90aW1lLCBhbHBoYQojIC0gS2V5c3Ryb2tlIGZlYXR1cmVzOiBkdXJhdGlvbl9tZWQsIHJwX21lZCwgcnJfbWVkLCBwcl9tZWQsIHBwX21lZCwgcHBfcGVyYzk1LCBwcF9tYWQsIGJhY2tzcGFjZV9yYXRlCiMgLSBCaWdyYW0tc3BlY2lmaWMgZmVhdHVyZXM6IGJpZ3JhbV8qCnZpbXBfbG9uZ1ssIGZlYXR1cmVfdHlwZSA6PSBjYXNlX3doZW4oCiAgZ3JlcGwoImNvcnJlY3R8cmVhY3Rpb25fdGltZXxhbHBoYSIsIHZhcmlhYmxlKSB+ICJMZWFybmluZyBwZXJmb3JtYW5jZSIsCiAgZ3JlcGwoImR1cmF0aW9uX21lZHxycF9tZWR8cnJfbWVkfHByX21lZHxwcF9tZWR8cHBfcGVyYzk1fHBwX21hZHxiYWNrc3BhY2VfcmF0ZSIsIHZhcmlhYmxlKSB+ICJLZXlzdHJva2UiLAogIGdyZXBsKCJiaWdyYW1fIiwgdmFyaWFibGUpIH4gIkJpZ3JhbS1zcGVjaWZpYyIsCiAgVFJVRSB+ICJPdGhlciIKKV0KCiMgUmVwbGFjZSBsZWFybmluZyBwZXJmb3JtYW5jZSBmZWF0dXJlcyBieSBtb3JlIGRlc2NyaXB0aXZlIG5hbWVzCnZpbXBfbG9uZ1tmZWF0dXJlX3R5cGUgPT0gIkxlYXJuaW5nIHBlcmZvcm1hbmNlIiwgdmFyaWFibGUgOj0gY2FzZV93aGVuKAogIHZhcmlhYmxlID09ICJjb3JyZWN0IiB+ICJDb3JyZWN0IiwKICB2YXJpYWJsZSA9PSAicmVhY3Rpb25fdGltZSIgfiAiUmVhY3Rpb24gdGltZSIsCiAgdmFyaWFibGUgPT0gImFscGhhIiB+ICJTcGVlZCBvZiBGb3JnZXR0aW5nIgopXQoKIyBSZXBsYWNlIGtleXN0cm9rZSBmZWF0dXJlcyBieSBtb3JlIGRlc2NyaXB0aXZlIG5hbWVzCnZpbXBfbG9uZ1tmZWF0dXJlX3R5cGUgPT0gIktleXN0cm9rZSIsIHZhcmlhYmxlIDo9IGNhc2Vfd2hlbigKICB2YXJpYWJsZSA9PSAiZHVyYXRpb25fbWVkIiB+ICJEdXJhdGlvbiAoaG9sZCB0aW1lKSIsCiAgdmFyaWFibGUgPT0gInJwX21lZCIgfiAiUmVsZWFzZS1QcmVzcyAoZmxpZ2h0IHRpbWUpIiwKICB2YXJpYWJsZSA9PSAicnJfbWVkIiB+ICJSZWxlYXNlLVJlbGVhc2UiLAogIHZhcmlhYmxlID09ICJwcl9tZWQiIH4gIlByZXNzLVJlbGVhc2UiLAogIHZhcmlhYmxlID09ICJwcF9tZWQiIH4gIlByZXNzLVByZXNzIChJS0QpIiwKICB2YXJpYWJsZSA9PSAicHBfcGVyYzk1IiB+ICJQcmVzcy1QcmVzcyAoSUtEKSA5NXRoIHBlcmNlbnRpbGUiLAogIHZhcmlhYmxlID09ICJwcF9tYWQiIH4gIlByZXNzLXByZXNzIE1BRCIsCiAgdmFyaWFibGUgPT0gImJhY2tzcGFjZV9yYXRlIiB+ICJCYWNrc3BhY2UgcmF0ZSIKKV0KCiMgRm9yIGJpZ3JhbS1zcGVjaWZpYyBmZWF0dXJlcywgd2UgY2FuIHJlcGxhY2UgdGhlIG51bWVyaWMgVW5pY29kZSB2YWx1ZXMgYnkgdGhlIGFjdHVhbCBiaWdyYW06IGJpZ3JhbV82OV84MiAtPiAiRSBSIgpwcmludF9iaWdyYW0gPC0gZnVuY3Rpb24gKHgpIHsKICAKICAjIE5vbi1wcmludGFibGUgY2hhcmFjdGVycyBhcmUgcmVwbGFjZWQgYnkgdGhlaXIgdGl0bGUKICBwcmludF9jaGFyIDwtIGZ1bmN0aW9uIChjaGFyKSB7CiAgICBjaGFyIDwtIGNhc2Vfd2hlbigKICAgICAgY2hhciA9PSA4IH4gIkJhY2tzcGFjZSIsCiAgICAgIGNoYXIgPT0gMTMgfiAiRW50ZXIiLAogICAgICBjaGFyID09IDE2IH4gIlNoaWZ0IiwKICAgICAgY2hhciA9PSAxNyB+ICJDdHJsIiwKICAgICAgY2hhciA9PSAyMCB+ICJDYXBzTG9jayIsCiAgICAgIGNoYXIgPT0gMzIgfiAiU3BhY2UiLAogICAgICBUUlVFIH4gaW50VG9VdGY4KGNoYXIpCiAgICApCiAgfQogIAogIGNoYXIxIDwtIHN0cl9leHRyYWN0KHgsICJcXGQrIikgfD4KICAgIGFzLm51bWVyaWMoKSB8PgogICAgcHJpbnRfY2hhcigpCiAgCiAgY2hhcjIgPC0gc3RyX2V4dHJhY3QoeCwgIlxcZCskIikgfD4KICAgIGFzLm51bWVyaWMoKSB8PgogICAgcHJpbnRfY2hhcigpIAoKICBwYXN0ZTAoY2hhcjEsICIgIiwgY2hhcjIpCn0KCnZpbXBfbG9uZ1tmZWF0dXJlX3R5cGUgPT0gIkJpZ3JhbS1zcGVjaWZpYyIsIHZhcmlhYmxlIDo9IG1hcF9jaHIodmFyaWFibGUsIHByaW50X2JpZ3JhbSldCgojIFJlb3JkZXIgdGhlIGZlYXR1cmUgdHlwZSBsZXZlbHMKdmltcF9sb25nWywgZmVhdHVyZV90eXBlIDo9IGZhY3RvcihmZWF0dXJlX3R5cGUsIGxldmVscyA9IGMoIkxlYXJuaW5nIHBlcmZvcm1hbmNlIiwgIktleXN0cm9rZSIsICJCaWdyYW0tc3BlY2lmaWMiKSldCgojIEhvdyBvZnRlbiBpcyBlYWNoIGZlYXR1cmUgYXZhaWxhYmxlIGluIHRoZSBkYXRhPwp2aW1wX2ZlYXR1cmVfYXZhaWxhYmlsaXR5IDwtIHZpbXBfbG9uZ1ssIC4oZmVhdHVyZV9hdmFpbGFiaWxpdHkgPSBmZWF0dXJlX2F2YWlsYWJpbGl0eVsxXSksIGJ5ID0gLih2YXJpYWJsZSwgZmVhdHVyZV90eXBlKV0KCmdncGxvdCh2aW1wX2xvbmdbIWlzLm5hKGltcG9ydGFuY2UpXSwgYWVzKHggPSByZW9yZGVyKHZhcmlhYmxlLCAoaW1wb3J0YW5jZSkpLCB5ID0gaW1wb3J0YW5jZSkpICsKICBnZW9tX3JlY3QoZGF0YSA9IE5VTEwsIGFlcyh4bWluID0gLUluZiwgeG1heCA9IEluZiwgeW1pbiA9IDEwMCwgeW1heCA9IEluZiksIGZpbGwgPSAid2hpdGUiLCBjb2xvdXIgPSAid2hpdGUiKSArCiAgZ2VvbV9qaXR0ZXIoYWVzKGNvbG91ciA9IGZlYXR1cmVfdHlwZSksIGFscGhhID0gLjEsIHNpemUgPSAuNzUpICsKICBnZW9tX2JveHBsb3QoYWVzKGZpbGwgPSBmZWF0dXJlX3R5cGUpLCB3aWR0aCA9IC41LCBhbHBoYSA9IC44LCBvdXRsaWVyLnNoYXBlID0gTkEpICsKICBhbm5vdGF0ZSgidGV4dCIsIHggPSBJbmYsIHkgPSAxMTAsIGxhYmVsID0gIkZlYXR1cmVcbmF2YWlsYWJpbGl0eToiLCBoanVzdCA9IDEsIHZqdXN0ID0gLS4yNSwgc2l6ZSA9IHJlbCgzKSwgY29sb3VyID0gImdyZXk0MCIpICsKICBnZW9tX3RleHQoZGF0YSA9IHZpbXBfZmVhdHVyZV9hdmFpbGFiaWxpdHksIGFlcyh5ID0gMTEwLCB4ID0gdmFyaWFibGUsIGxhYmVsID0gZmVhdHVyZV9hdmFpbGFiaWxpdHkpLCBoanVzdCA9IDEsIHZqdXN0ID0gLjUsIHNpemUgPSByZWwoMyksIGNvbG91ciA9ICJncmV5NDAiKSArCiAgbGFicyh4ID0gIkZlYXR1cmUiLAogICAgICAgeSA9ICJWYXJpYWJsZSBpbXBvcnRhbmNlIChzY2FsZWQpIiwKICAgICAgIGNvbG91ciA9ICJGZWF0dXJlIHR5cGUiLAogICAgICAgZmlsbCA9ICJGZWF0dXJlIHR5cGUiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgMTEwKSwgYnJlYWtzID0gc2VxKDAsIDEwMCwgMjUpKSArCiAgc2NhbGVfY29sb3VyX2JyZXdlcihwYWxldHRlID0gIkRhcmsyIikgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiRGFyazIiKSArCiAgY29vcmRfZmxpcChjbGlwID0gIm9mZiIpICsKICB0aGVtZV9tbCgpICsKICB0aGVtZShheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gcmVsKC43NSkpLAogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQoKZ2dzYXZlKGhlcmUoIm91dHB1dCIsICJ2YXJpYWJsZV9pbXBvcnRhbmNlLnBuZyIpLCB3aWR0aCA9IDksIGhlaWdodCA9IDkpCmBgYAoKCiMjIEZlYXR1cmUgY29ycmVsYXRpb24KClBhaXJ3aXNlIGNvcnJlbGF0aW9uIGJldHdlZW4gdGhlIGZlYXR1cmVzIGluIHRoZSBkYXRhOgpgYGB7ciBmZWF0dXJlLWNvcnJlbGF0aW9ufQpjb3IoZGZbLCAtYygicmVzcG9uc2VfaWQiLCAidXNlcl9pZCIsICJzZXNzaW9uX2lkIildLCB1c2UgPSAicGFpcndpc2UuY29tcGxldGUub2JzIikgfD4KICBnZ2NvcnJwbG90KCkKYGBg